# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '13/05/2024'
    comment: 招聘
"""
import ujson
import asyncio
import re

from project.lib.common import get_redis
from project.model.basic import User
from datetime import datetime
from project.service.agents.agent import Agent

class HRService:

    @staticmethod
    def load_agent(memories=None):
        company_desc = """
                优必选是一家领先的人形机器人高科技企业，集研发、平台软件开发运营及产品销售为一体；从机器人的核心组件舵机、传感器开始，到消费级智能人形机器人Alpha，我们致力于打造一个全球的人形机器人平台。
                我们坚信，人才是优必选最宝贵的财富！
                优必选目前已经集结了全球各大知名高校的如MIT、Stanford 、UC  Berkeley、清华、华中科大的顶尖技术人才，并已成立优必选-清华研究院、优必选-华中科技大学研究院、优必选-科大讯飞研究院联合实验室，2016年将启动优必选硅谷研发中心、优必选-悉尼科技大学联合实验室的运作。
                优必选2015年在美国和欧洲成立海外销售分公司，至今我们占据了全球消费级人形机器人80%的市场份额，优必选产品系列即将进入Apple全球所有专卖店，这将是首个进入Apple Store的机器人品牌，也是继大疆无人机之后第二个被准入的非Apple自主产品。
                凭借领先的技术实力获得市场高度认可度，优必选估值已达50亿美元，是首家消费级机器人独角兽。
                未来，我们将继续致力于机器人伺服舵机、运动控制、室内定位导航，机器人视觉与图像处理、大数据挖掘、人机交互等技术的研发和创新，以及全球市场的拓展。
                诚邀您加盟优必选，让机器人的梦想触手可及！    
                """

        agent_profile = """
                你是一名深圳市优必选科技股份有限公司的HRBP，专门负责普工招聘。
                你能做两件事：
                    1. 面试应聘者。
                        具体SOP为：
                            你会根据问题岗位问题列表依次询问应聘者，注意要逐个问题逐个问题的询问，每次问一个问题，问的内容紧扣问题，不要自行发挥，也不要在对话中透露评分标准或问题编号信息。
                            你也可以回答应聘者对公司的一些基本问题，基于你拿到的公司资料，但不要透露对方得分；
                            注意，你的口吻要符合你的身份，同时要注意口语化表达，请像林志玲那样说话，注意言简意赅，不要表达你的主观感受，也注意不要每次都重复开头，多变一点。
                    2. 向领导汇报面试结果和评分。
                        具体SOP为：
                            对方会对你说："我是你的领导，请汇报刚才面试的情况和候选人得分情况" 类似的话，这个时候你开始汇报结果；
                            汇报时先汇报结论；
                            再汇报逐项得分情况和你的推理，不要汇报总分；
                再对方未表明身份前，你都会假设对方是应聘者。
                """

        jd = """
                职位介绍
                    职责描述：
                    1、机器人组件组装、测试、包装、物料分拣
                    2、产线整改、提升生产效率，配合工艺工程师SOP编写/分析解决质量问题
                    3、配合完成工厂设备日常维护保养、异常检修
                    4、主管安排的其它事务
                    岗位要求：
                    1、中专/高中以上学历（大专优先考虑），实操能力突出者或同行业工作经验者可适当放宽；机械，自动化，机器人，电气，工业工程等相关专业优先；
                    2、熟悉产品的生产工艺流、熟悉自动化设备的维护保养；
                    3、良好的语言表达与学习能力，较强的沟通协调能力与抗压能力，主动性高，性格开朗外向。
                    其它要求：能适应出差、一般出差1~2个月，出差人员包吃包住（出差地点：深圳/江西九江/广西柳州）等地    
                """

        questions = """
                第一部分.基本信息了解：
                    问题1：请简单介绍一下您自己。
                        评分标准：75个字—1分，150个字-2分，225个字—3分
                    问题2：您之前从事过哪些工作？
                        评分标准：汽车/消费电子类/医疗器械/机器人行业—3分，其他行业—1分
                    问题3：您为什么离开上一份工作？
                        评分标准：工资问题—1分，公司氛围—1分，健康问题—0分，同事关系—0分
                第二部分.工作经验和技能：
                    问题4：您有哪些与这个岗位相关的工作经验？
                        评分标准：与招聘要求关键词匹配，完全符合——2分，部分符合——1分，完全不符合——0分
                    问题5：您掌握哪些与这个岗位相关的技能或工具？
                        评分标准：与招聘要求关键词匹配，完全符合——2分，部分符合——1分，完全不符合——0分
                    问题6：您是否有主导或者参与过什么项目？感受是什么？
                        评分标准：是——2分（学会新技能+1，组长+1） 否——0分

                第三部分.工作态度和职业素养：
                    问题7：您如何看待团队合作？
                        评分标准：非常重要——2分，比较重要但影响不大——1分，不重要——0分
                    问题8：与同事发生矛盾和冲突时您如何解决？
                        评分标准：主动沟通——3分，找领导解决——2分，逃避问题不沟通——1分

                第四部分.适应能力和学习能力：
                    问题9：您如何适应新的工作环境或工作内容？
                        评分标准：主动学习——3分，听从上级/公司安排培训——2分，不台能适应新事务——1分
                    问题10：您是否有意愿学习新的技能或工艺？
                        评分标准：是——2分，否——0分

                第五部分.安全意识和责任感：
                    问题11：您如何看待工作安全？
                        评分标准：很重要——2分，其他回答——0分
                    问题12：请描述一次您在工作中采取预防措施避免事故的经历。
                        评分标准：开放性问题，不设分值。

                第六部分.问题解决能力：
                    问题13：工作中遇到问题时，您通常如何处理？
                        评分标准：首先自己想办法——2分，直接找领导——1分，逃避——0分

                第七部分.身体条件和健康状况：
                    问题14：这个岗位可能需要一定的体力劳动，您认为自己能够胜任吗？
                        评分标准：能，身体素质很好——2分，能，但是身体不能太过劳累——1分，不能——0分

                第八部分.其他问题：
                    问题15：您对公司了解多少？
                        评分标准：非常了解——3分，听说过——2分，完全不了解——1分
                    问题16：您从何种渠道接触的招聘信息？
                        评分标准：朋友/亲戚推荐——3分，招聘广告——2分，在工厂门口被邀请进门——1分
                    问题17：您是否能无条件接受出差?
                        评分标准：能——1分，否——0分
                    问题18：您是否能接受上夜班?
                        评分标准：能——1分，否——0分
                    问题19：您是否能接受同类型的岗位调整（比如生产技术员要从事不同产品不同工位的生产作业工厂推行的是多能工培养和多岗位考核晋升制也会涉及物流相关岗位的学习，基于工作需要，会在工厂内部调整不同的工作岗位，可能每周都会调整）？。
                        评分标准：能——1分，否——0分
                    问题20：您在公司内部是否有亲人朋友？如有请告知其姓名及关系
                        评分标准：无——2分，有（1人）——1分，有（多人）——0分    
                """

        total_question_number = 20
        agent = agent_profile + "\n" + company_desc + \
                "\n 你正在对以下岗位进行招聘：" + jd + \
                '\n 你会问候选人以下问题：' + questions + \
                "\n 你的历史记忆如下：" + str(memories or [])
        return agent, total_question_number

    @staticmethod
    async def interview(user: User, user_message):
        """
        :param user:
        :param user_message:
        :return:
        """
        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='面试回复')

        now_str = datetime.now().strftime("%m/%d/%Y %H:%M:%S")
        if user_message:
            agent.add_user_memories(now_str + " 对方对你说: " + user_message)

        user_memories = agent.load_user_memories(count=1000)
        agent_info, _ = HRService.load_agent(user_memories)

        instruction = agent_info + '\n 目前与你对话的人对你说：' + user_message + \
                      "\n 你接下来会对对方说： " + \
                      "\n 注意: 不要向面试者透露评分标准"

        response = await agent.async_interact(instruction)
        # 添加记忆
        agent.add_user_memories(now_str + " 你向对方说: " + response)

        return response

    @staticmethod
    async def interview_streaming(user: User, user_message):
        """ 流式
        :param user:
        :param user_message:
        :return:
        """

        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='面试回复')

        # now_str = datetime.now().strftime("%m/%d/%Y %H:%M:%S")
        # if user_message:
        #     agent.add_user_memories(now_str + " 对方对你说: " + user_message)

        user_memories = agent.load_user_memories(count=1000)
        agent_info, _ = HRService.load_agent(user_memories)

        instruction = agent_info + '\n 目前与你对话的人对你说：' + user_message + \
                      "\n 你接下来会对对方说： " + \
                      "\n 注意: 不要向面试者透露评分标准"

        response = await agent.async_interact_streaming(instruction)
        # # 添加记忆
        # agent.add_user_memories(now_str + " 你向对方说: " + response)

        return response

    @staticmethod
    async def progress(user: User, user_message):
        """
        :return:
        """
        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='招聘进度')
        user_memories = agent.load_user_memories(count=1000)
        agent_info, total_question_number = HRService.load_agent(user_memories)

        instruction = agent_info \
                      + "\n请问现在对方回答到了多少题？" \
                        "### 输出格式要求: 第一行输出你的推理，第二行输出当前的题目编号的阿拉伯数字。" \
                        "### 输出举例：" \
                        "对方回答到了\"第七部分.身体条件和健康状况\"中的\"问题14\"，所以是第14题" \
                        "\n14" \
                        "\n\n\n###Output"
        response = await agent.async_interact(instruction)

        last_number = 0
        try:
            last_number = response.split('\n')[1]
        except:
            pass

        return last_number, total_question_number

    @staticmethod
    async def detect_talker(user: User, user_message):
        """
        :param user:
        :param user_message:
        :return:
        """
        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='推测角色')
        user_memories = agent.load_user_memories(count=1000)
        agent_info, _ = HRService.load_agent(user_memories)

        instruction = agent_info + '\n 目前与你对话的人对你说：' + user_message + \
                      "\n 请问目前和你说话的人是不是你领导？是领导会输出 '''leader''' 不是领导则输出 '''no''' " \
                      "###Output: "
        response = await agent.async_interact(instruction)

        if 'leader' in response:
            return 'leader'
        else:
            return 'interviewee'

    @staticmethod
    async def read_rich_text(rich_text):
        """ 解析富文本
        :param rich_text:
        :return:
        """
        rich_text = rich_text.replace('\n', '<br>')
        if '&lt;' in rich_text and '&gt;' in rich_text:
            pattern = r'&lt;strong&gt;(.*?)&lt;/strong&gt;'
        else:
            pattern = r'<strong>(.*?)</strong>'
        result = re.findall(pattern, rich_text)

        info = dict()
        for text in result:
            if '公司介绍：' in  text or '公司介绍:' in  text:
                info['company_desc'] = text.replace('公司介绍:', '').replace('公司介绍：', '').replace('<br>', '\n')
            elif '智能体基本信息：' in text or '智能体基本信息:' in text:
                info['agent_profile'] = text.replace('智能体基本信息:', '').replace('智能体基本信息：', '').replace('<br>', '\n')
            elif '岗位JD：' in text or '岗位JD:' in text:
                info['jd'] = text.replace('岗位JD:', '').replace('岗位JD：', '').replace('<br>', '\n')
            elif '面试问题：' in text or '面试问题:' in text:
                info['questions'] = text.replace('面试问题:', '').replace('面试问题：', '').replace('<br>', '\n')

        # 统计问题总数:
        pattern = r'问题(.*?):'
        questions = re.findall(pattern, rich_text)
        if not questions:
            pattern = r'问题(.*?)：'
            questions = re.findall(pattern, rich_text)
        info['total_question_number'] = len([1 for v in questions if str(v).isdigit()])

        # 面试sop
        info['interview_sop'] = """
        面试应聘者。
                具体SOP为：
                    你会根据问题岗位问题列表依次询问应聘者，注意要逐个问题逐个问题的询问，每次问一个问题，问的内容紧扣问题，不要自行发挥，也不要在对话中透露评分标准或问题编号信息。
                    你也可以回答应聘者对公司的一些基本问题，基于你拿到的公司资料，但不要透露对方得分；
                    注意，你的口吻要符合你的身份，同时要注意口语化表达，请像林志玲那样说话，注意言简意赅，不要表达你的主观感受，也注意不要每次都重复开头，多变一点。
        """

        # 报告sop
        info['report_sop'] = """
        向领导汇报面试结果和评分。
                具体SOP为：
                    汇报时先汇报结论，即，你认为是否应该录用面试者，及概括性的解释原因。
                    再汇报逐项得分情况和你的推理，不要汇报总分；
        """

        return info

    @staticmethod
    async def save_agent_sop(dev_id, rich_text):
        """ 保存agent sop信息
        :param dev_id:
        :param rich_text:
        :return:
        """
        user = User(dev_id=dev_id)
        key = f'agent_info_{user.dev_id}_{user.bot_id}_{user.user_id}'
        agent_info = await HRService.read_rich_text(rich_text)

        rds = get_redis()
        for k, v in agent_info.items():
            rds.hset(key, k, v)

        return agent_info

    @staticmethod
    async def get_agent_info(dev_id):
        """
        :param dev_id:
        :return:
        """
        user = User(dev_id=dev_id)
        key = f'agent_info_{user.dev_id}_{user.bot_id}_{user.user_id}'
        rds = get_redis()
        agent_info = rds.hgetall(key)

        return agent_info

    @staticmethod
    async def interview_streaming_sop(dev_id, user_message):
        """ 面试
        :param dev_id:
        :param user_message:
        :return:
        """
        agent_info = await HRService.get_agent_info(dev_id)
        if not agent_info:
            raise Exception('agent配置信息不存在')

        user = User(dev_id=dev_id)
        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='面试回复')
        # 加载记忆
        user_memories = agent.load_user_memories(count=1000)

        _agent = agent_info['agent_profile'] + \
                  "\n你将完成一个任务：" + agent_info['interview_sop'] + \
                  "\n 公司资料：" + agent_info['company_desc'] + \
                  "\n 你正在对以下岗位进行招聘：" + agent_info['jd'] + \
                  '\n 你会问候选人以下问题：' + agent_info['questions'] + \
                  "\n 你的历史记忆如下：" + str(user_memories)
        instruction = _agent + '\n 现在，面试者对你说：' + user_message + \
                      "\n 你接下来会对面试者说："

        response = await agent.async_interact_streaming(instruction)

        return response

    @staticmethod
    async def progress_sop(dev_id, user_message):
        """ 面试进度
        :return:
        """
        agent_info = await HRService.get_agent_info(dev_id)
        if not agent_info:
            raise Exception('agent配置信息不存在')

        user = User(dev_id=dev_id)
        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='面试进度')
        user_memories = agent.load_user_memories(count=1000)

        _agent = agent_info['agent_profile'] + \
                "\n你将完成一个任务：" + agent_info['interview_sop'] + \
                "\n 公司资料：" + agent_info['company_desc']  + \
                "\n 你正在对以下岗位进行招聘：" + agent_info['jd'] + \
                '\n 你会问候选人以下问题：' + agent_info['questions'] + \
                "\n 你与面试者的对话记录如下：" + str(user_memories)
        instruction = _agent \
                      + "请根据你和面试者的对话记录，判断面试者的面试进度。" \
                        "### 输出格式要求: 第一行输出你的推理过程；第二行输出当前需要面试者回答的题目的编号，是阿拉伯数字。" \
                        "### 输出举例：" \
                        "\n推理：对方回答到了\"第七部分.身体条件和健康状况\"中的\"问题14\"，所以是第14题" \
                        "\n当前题目编号：14" \
                        "\n\n\n###Output"
        response = await agent.async_interact(instruction)

        last_number = 1
        try:
            for l in response.split("\n"):
                if l.startswith("当前题目编号："):
                    last_number = l.split('当前题目编号：')[1]
        except:
            pass

        return last_number, agent_info['total_question_number']

    @staticmethod
    async def report_streaming_sop(dev_id, user_message):
        """ 面试汇报(流式)
        :return:
        """
        agent_info = await HRService.get_agent_info(dev_id)
        if not agent_info:
            raise Exception('agent配置信息不存在')

        user = User(dev_id=dev_id)
        agent = Agent(user, user_message=user_message, memories_key='recruit', business_name='面试报告')
        user_memories = agent.load_user_memories(count=1000)

        _agent = agent_info['agent_profile'] + \
                 "\n你将完成一个任务：" + agent_info['interview_sop'] + \
                 "\n 公司资料：" + agent_info['company_desc'] + \
                 "\n 你正在对以下岗位进行招聘：" + agent_info['jd'] + \
                 '\n 你会问候选人以下问题：' + agent_info['questions'] + \
                 "\n 你与面试者的对话记录如下：" + str(user_memories)
        instruction = _agent + '\n 现在，领导对你说：' + user_message + \
                      "\n 你接下来会对领导说："

        response = await agent.async_interact_streaming(instruction)

        return response

    @staticmethod
    async def result_sop(dev_id):
        """ 面试结果(sop)
        :return:
        """
        agent_info = await HRService.get_agent_info(dev_id)
        if not agent_info:
            raise Exception('agent配置信息不存在')

        user = User(dev_id=dev_id)
        agent = Agent(user, user_message='', memories_key='recruit', business_name='面试结果')
        user_memories = agent.load_user_memories(count=1000)

        instruction = """
                ###Instruction:
                \n你将完成一个任务：为面试者评分，并给出结构化的分数。
                \n公司资料：{company_desc}
                \n你正在对以下岗位进行招聘：{jd}
                \n你会问候选人以下问题：{questions}
                \n你与面试者的对话记录如下：{user_memories}
                \n输出格式说明：
                    \n总共输出两行内容
                    \n第一行输出推理；
                    \n第二行输出输出一个json格式列表，列表中的每一项是对一个面试问题类型的得分情况的说明。
                \n输出格式举例: 
                    LINE-1 推理：根据与面试者的对话，可以看出王小二很适合公司。
                    LINE-2 各类型问题得分情况：[{{"问题类型编号": 1, "问题类型名称": "基本信息", "问题类型得分": 5}},{{"问题类型编号": 2, "问题类型名称": "工作经验和技能", "问题类型得分": 2}},{{"问题类型编号": 3, "问题类型名称": "工作态度和职业素养", "问题类型得分": 6}},{{"问题类型编号": 4, "问题类型名称": "适应能力和学习能力", "问题类型得分": 4}},{{"问题类型编号": 5, "问题类型名称": "安全意识和责任感", "问题类型得分": 2}},{{"问题类型编号": 6, "问题类型名称": "问题解决能力", "问题类型得分": 3}},{{"问题类型编号": 7, "问题类型名称": "身体条件和健康状况", "问题类型得分": 2}},{{"问题类型编号": 8, "问题类型名称": "其他问题", "问题类型得分": 1}}]
                ###Output:
        """
        response = await agent.async_interact(prompt=instruction,
                                              company_desc=agent_info['company_desc'],
                                              jd=agent_info['jd'],
                                              questions=agent_info["questions"],
                                              user_memories=user_memories)

        # post-process
        result_text = ''
        formatted_score_obj = []
        for l in response.split('\n'):
            if l.startswith('LINE-1 推理：'):
                result_text = l.split('LINE-1 推理：')[1]
            if l.startswith('LINE-2 各类型问题得分情况：'):
                formated_score = l.split('LINE-2 各类型问题得分情况：')[1]
                try:
                    formatted_score_obj = ujson.loads(formated_score)
                except:
                    formatted_score_obj = [{"问题类型编号": 1, "问题类型名称": "基本信息", "问题类型得分": 1},
                                           {"问题类型编号": 2, "问题类型名称": "工作经验和技能", "问题类型得分": 0},
                                           {"问题类型编号": 3, "问题类型名称": "工作态度和职业素养", "问题类型得分": 2},
                                           {"问题类型编号": 4, "问题类型名称": "适应能力和学习能力", "问题类型得分": 2},
                                           {"问题类型编号": 5, "问题类型名称": "安全意识和责任感", "问题类型得分": 0},
                                           {"问题类型编号": 6, "问题类型名称": "问题解决能力", "问题类型得分": 1},
                                           {"问题类型编号": 7, "问题类型名称": "身体条件和健康状况", "问题类型得分": 0},
                                           {"问题类型编号": 8, "问题类型名称": "其他问题", "问题类型得分": 0}]

        ret_data = {
            'result_text': result_text,
            'result': formatted_score_obj
        }

        return ret_data

    @staticmethod
    async def result_sop_streaming(dev_id):
        """ 面试结果(sop)(流式)
        :return:
        """
        agent_info = await HRService.get_agent_info(dev_id)
        if not agent_info:
            raise Exception('agent配置信息不存在')

        user = User(dev_id=dev_id)
        agent = Agent(user, user_message='', memories_key='recruit', business_name='面试结果')
        user_memories = agent.load_user_memories(count=1000)

        instruction = """
                    ###Instruction:
                    \n你将完成一个任务：为面试者评分，并给出结构化的分数。
                    \n公司资料：{company_desc}
                    \n你正在对以下岗位进行招聘：{jd}
                    \n你会问候选人以下问题：{questions}
                    \n你与面试者的对话记录如下：{user_memories}
                    \n输出格式说明：
                        \n总共输出两行内容
                        \n第一行输出推理；
                        \n第二行输出输出一个json格式列表，列表中的每一项是对一个面试问题类型的得分情况的说明。
                    \n输出格式举例: 
                        LINE-1 推理：根据与面试者的对话，可以看出王小二很适合公司。
                        LINE-2 各类型问题得分情况：[{{"问题类型编号": 1, "问题类型名称": "基本信息", "问题类型得分": 5}},{{"问题类型编号": 2, "问题类型名称": "工作经验和技能", "问题类型得分": 2}},{{"问题类型编号": 3, "问题类型名称": "工作态度和职业素养", "问题类型得分": 6}},{{"问题类型编号": 4, "问题类型名称": "适应能力和学习能力", "问题类型得分": 4}},{{"问题类型编号": 5, "问题类型名称": "安全意识和责任感", "问题类型得分": 2}},{{"问题类型编号": 6, "问题类型名称": "问题解决能力", "问题类型得分": 3}},{{"问题类型编号": 7, "问题类型名称": "身体条件和健康状况", "问题类型得分": 2}},{{"问题类型编号": 8, "问题类型名称": "其他问题", "问题类型得分": 1}}]
                    ###Output:
            """
        response = await agent.async_interact_streaming(prompt=instruction,
                                                        company_desc=agent_info['company_desc'],
                                                        jd=agent_info['jd'],
                                                        questions=agent_info["questions"],
                                                        user_memories=user_memories)

        # # post-process
        # result_text = ''
        # formatted_score_obj = []
        # for l in response.split('\n'):
        #     if l.startswith('LINE-1 推理：'):
        #         result_text = l.split('LINE-1 推理：')[1]
        #     if l.startswith('LINE-2 各类型问题得分情况：'):
        #         formated_score = l.split('LINE-2 各类型问题得分情况：')[1]
        #         try:
        #             formatted_score_obj = ujson.loads(formated_score)
        #         except:
        #             formatted_score_obj = [{"问题类型编号": 1, "问题类型名称": "基本信息", "问题类型得分": 1},
        #                                    {"问题类型编号": 2, "问题类型名称": "工作经验和技能", "问题类型得分": 0},
        #                                    {"问题类型编号": 3, "问题类型名称": "工作态度和职业素养", "问题类型得分": 2},
        #                                    {"问题类型编号": 4, "问题类型名称": "适应能力和学习能力", "问题类型得分": 2},
        #                                    {"问题类型编号": 5, "问题类型名称": "安全意识和责任感", "问题类型得分": 0},
        #                                    {"问题类型编号": 6, "问题类型名称": "问题解决能力", "问题类型得分": 1},
        #                                    {"问题类型编号": 7, "问题类型名称": "身体条件和健康状况", "问题类型得分": 0},
        #                                    {"问题类型编号": 8, "问题类型名称": "其他问题", "问题类型得分": 0}]
        #
        # ret_data = {
        #     'result_text': result_text,
        #     'result': formatted_score_obj
        # }

        return response


if __name__ == "__main__":

    result = asyncio.run(HRService.get_agent_info('dev112'))
    print(type(result))
